Skip to content

feat(map-efficient): per-subtask git worktree isolation (Part of #284)#296

Merged
azalio merged 1 commit into
mainfrom
feat/284-worktree-isolation
Jun 27, 2026
Merged

feat(map-efficient): per-subtask git worktree isolation (Part of #284)#296
azalio merged 1 commit into
mainfrom
feat/284-worktree-isolation

Conversation

@azalio

@azalio azalio commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Part of #284 — Phase 1, Slice 1.

What

Opt-in (worktree.isolation: true), OFF by default, per-subtask git worktree isolation for /map-efficient. Each subtask's Actor runs in a dedicated throwaway git worktree; the result is squash-merged back into the working branch only after the configured verification_checks pass IN the worktree (a pre-merge gate). A rejected attempt (Monitor valid=false / Evaluator fail) is discarded, so the working branch is never touched by a bad attempt.

Why

/map-efficient shares one accumulated session context across all subtasks; #284 is "the second-largest structural gap after spec-driven pipeline". This lands the foundational Phase-1 slice: filesystem isolation + atomic accept/reject + safe rollback (the base Phase 2 parallelism needs).

Design (llm-council-reviewed, conv 461b92f9)

  • Runner-owned, not harness-native isolation="worktree" — native hides the worktree path, making deterministic safety gates and explicit merge-back impossible. The two mechanisms must never both be active.
  • Accept = git merge --squash + one runner-authored commit (never --no-ff, which would break one-commit-per-subtask).
  • Always discard on reject (atomic; retry starts from a clean HEAD).
  • Pre-merge verify + crash-safe retry + atomic reject folded in so the slice is not a no-op (council Q5).
  • State-root separation (council Q6 blocker): worktrees stored OUT of the working tree under the repo's git common dir; MAP state always resolves against the main checkout; state-mutating commands refuse if invoked from inside a managed worktree.

Step-runner commands (producer-owns-parse; structured {kind,message})

  • create_subtask_worktree — crash-safe; guards: not-a-repo, protected-ref, nested-worktree, active-git-op, subtask_id ref/path sanitization, dirty-main (excludes MAP runtime state), submodule init.
  • merge_subtask_worktree — base-divergence, runtime-state-in-diff, bulk-deletion (worktree.max_deletions), submodule-pointer, detached-HEAD guards BEFORE touching the working branch, then pre-merge verify, then squash-merge.
  • discard_subtask_worktree — atomic reject, idempotent, optional --save-patch.
  • worktree_isolation_status — reconcile recorded vs live worktrees.

New worktree manifest stage + .map/<branch>/worktrees.json sidecar. Config keys worktree.{isolation,max_deletions}.

Tests

tests/test_worktree_isolation.py30 tests: config (defaults/aliasing/validation/doc), full lifecycle, disabled no-op, and every guard (VERIFY_FAILED leaves main untouched, BULK_DELETION, BASE_DIVERGED, PROTECTED_REF, DIRTY_MAIN + runtime-state exclusion, INVALID_SUBTASK_ID, no_changes detection, crash-safe recreate) against real throwaway git repos.

Gate

Local make check green: ruff + mypy (Success) + pyright (0 errors/0 warnings/0 informations) + 2910 tests passed, 3 skipped + render check (generated trees match templates_src). Single-source render invariant honored (edited .jinja, ran make render-templates; the always-loaded SKILL.md body budget was bumped 508→515 for the irreducible active wiring, full recipe in efficient-reference.md).

Note: GitHub Actions CI is currently unavailable (billing), so this was validated via the local gate.

Deferred (issue stays open)

  • Phase 2: wave/DAG parallelism + files_modified overlap detection.
  • Phase 3: context-budget hooks (statusline, warnings, heartbeat).

Opt-in, OFF by default (`worktree.isolation: true`). When enabled,
`/map-efficient` runs each subtask's Actor in a dedicated throwaway git
worktree and squash-merges the result back into the working branch ONLY
after the configured `verification_checks` pass IN the worktree (pre-merge
gate). A rejected attempt (Monitor valid=false / Evaluator fail) is
discarded, so the working branch is never touched by a bad attempt.

Runner-owned (not harness-native isolation="worktree"); the step runner owns
the full lifecycle + every safety guard, returning structured {kind,message}:
- create_subtask_worktree: crash-safe remove-and-recreate; guards for
  not-a-repo, protected-ref, nested-worktree refusal, active-git-op,
  subtask_id ref/path sanitization, dirty-main (excludes MAP runtime state),
  submodule init.
- merge_subtask_worktree: base-divergence, runtime-state-in-diff,
  configurable bulk-deletion (worktree.max_deletions), submodule-pointer,
  detached-HEAD guards run BEFORE the working branch is touched; accept =
  git merge --squash + one runner-authored commit (never --no-ff).
- discard_subtask_worktree: atomic reject, idempotent, optional --save-patch.
- worktree_isolation_status: reconcile recorded vs live worktrees.

Worktrees are stored out of the working tree under the repo's git common dir,
immune to git clean -fdx / recursive scanners / accidental commits. MAP state
(.map/<branch>/...) always resolves against the main checkout. New `worktree`
manifest stage + .map/<branch>/worktrees.json sidecar.

Config keys worktree.{isolation,max_deletions} (dotted-YAML alias). Skill
wiring in map-efficient (active branches; full recipe in
efficient-reference.md). Docs: CHANGELOG/USAGE/ARCHITECTURE. 30 new tests
exercise the lifecycle + every guard against real throwaway git repos.

Design was llm-council-reviewed (runner-owned over harness-native;
squash-merge over --no-ff; always-discard on reject; pre-merge verify +
crash-safe retry + atomic reject folded in so the slice is not a no-op;
explicit state-root separation).

Phase 2 (wave/DAG parallelism) and Phase 3 (context-budget hooks) remain
open on #284.
@azalio azalio merged commit 7291d07 into main Jun 27, 2026
6 checks passed
@azalio azalio deleted the feat/284-worktree-isolation branch June 27, 2026 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant